﻿using NLog;
using Dapper;
using System;
using System.Linq;
using Web.Bid.Models;
using System.Threading;
using System.Configuration;
using System.Data.SqlClient;
using Microsoft.AspNet.SignalR;
using System.Collections.Generic;
using Microsoft.AspNet.SignalR.Hubs;


namespace Web.Bid.Hubs
{
    public class AuctionNotifier
    {
        private int ipAddressLimit;
        private int connectionLimit;

        private readonly static object MarketStateLock = new object();

        private readonly static IDictionary<long, Auction> Auctions = new Dictionary<long, Auction>(50);

        private readonly static IDictionary<AuctionGrade, long[]> TransfiniteUsers = new Dictionary<AuctionGrade, long[]>(6);

        private readonly static Lazy<AuctionNotifier> LazyInstance = new Lazy<AuctionNotifier>(() => new AuctionNotifier());

        private const int UpdateInterval = 1000; //ms

        // This is used as an singleton instance so we'll never both disposing the timer
        private Timer timer;
        private bool updatingStatus;
        private readonly object updateStatusLock = new object();

        private MarketState marketState = MarketState.Closed;

        private readonly Lazy<IHubConnectionContext> clientsInstance = new Lazy<IHubConnectionContext>(() => GlobalHost.ConnectionManager.GetHubContext<AuctionHub>().Clients);

        private readonly string openTime;
        private readonly string closeTime;

        private readonly string connectionString;

        private static readonly Logger Logger = LogManager.GetLogger("log");

        private AuctionNotifier()
        {
            ipAddressLimit = 2;
            connectionLimit = 5;
            openTime = ConfigurationManager.AppSettings["openTime"] ?? "05:00:00";
            closeTime = ConfigurationManager.AppSettings["closeTime"] ?? "23:59:59";
            connectionString = ConfigurationManager.ConnectionStrings["SilenthinkAuction"].ConnectionString;
        }

        public static AuctionNotifier Instance
        {
            get
            {
                return LazyInstance.Value;
            }
        }

        private IHubConnectionContext Clients
        {
            get { return clientsInstance.Value; }
        }

        public DateTime OpenTime
        {
            get { return DateTime.Parse(openTime); }
        }
        public DateTime CloseTime
        {
            get { return DateTime.Parse(closeTime); }
        }
        public MarketState MarketState
        {
            get { return marketState; }
            private set { marketState = value; }
        }
        public int IpAddressLimit
        {
            get { return ipAddressLimit; }
            private set { ipAddressLimit = value; }
        }
        public int ConnectionLimit
        {
            get { return connectionLimit; }
            private set { connectionLimit = value; }
        }
        public bool ManualClosed { get; set; }

        public IList<Auction> GetAllAuctions()
        {
            return Auctions.Values.ToList();
        }

        public void OpenMarket()
        {
            if (MarketState != MarketState.Open || MarketState != MarketState.Opening)
            {
                lock (MarketStateLock)
                {
                    if (MarketState != MarketState.Open || MarketState != MarketState.Opening)
                    {
                        MarketState = MarketState.Opening;
                        LoadDefaultAuctions();
                        LoadDefaultTransfiniteUsers();
                        timer = new Timer(UpdateStatus, null, UpdateInterval, UpdateInterval);
                        MarketState = MarketState.Open;
                        //BroadcastMarketStateChange(MarketState.Open);
                    }
                }
            }
        }

        public void CloseMarket()
        {
            if (MarketState == MarketState.Open || MarketState == MarketState.Opening)
            {
                lock (MarketStateLock)
                {
                    if (MarketState == MarketState.Open || MarketState == MarketState.Opening)
                    {
                        MarketState = MarketState.Closing;
                        if (timer != null)
                        {
                            timer.Dispose();
                        }
                        TransfiniteUsers.Clear();
                        MarketState = MarketState.Closed;
                        //BroadcastMarketStateChange(MarketState.Closed);
                    }
                }
            }
        }

        //public void Reset()
        //{
        //    lock (MarketStateLock)
        //    {
        //        if (MarketState != MarketState.Closed)
        //        {
        //            throw new InvalidOperationException("Market must be closed before it can be reset.");
        //        }
        //        //_stocks.Clear();
        //        //LoadDefaultStocks();
        //        //BroadcastMarketStateChange(MarketState.Reset);
        //    }
        //}

        public bool CheckTime(DateTime now)
        {
            return (OpenTime <= now && now <= CloseTime);
        }

        public SkResult<object> Bid(int auctionId, long userId, string username, string nickname, string ipaddess)
        {
            var now = DateTime.Now;

            var result = new SkResult<object>();

            var auction = GetAuction(auctionId);

            if (now < auction.StartDateTime)
            {
                result.Message = "不在竞拍时间内！";
                return result;
            }
            if (auction.Status == AuctionStatus.Dealt)
            {
                result.Message = "该拍品已成交！";
                return result;
            }
            if (userId == auction.CurrentUserId)
            {
                result.Message = "不可连续出价！";
                return result;
            }
            var status = TransfiniteUsers.FirstOrDefault(o => o.Key == auction.Grade).Value.Contains(userId);
            if (status)
            {
                result.Message = "您已超限！";
                return result;
            }
            //if(auction.ZeroSupported)
            //{
            //    //0元竞购
            //}
            //else
            //{
            //    if(auction.FreeBidSupported)
            //    {
            //        if(auction.FreeBidLimitation==FreeBidLimitation.Unlimited)
            //        {
            //            //先消耗免费拍币再消耗付费拍币
            //        }
            //        else if(auction.FreeBidLimitation==FreeBidLimitation.NTimesOnly)
            //        {
            //            if(auction.FreeBidCount>auction.FreeBidLimitationValue)
            //            {
            //                result.Message = "您使用已超限！";
            //            }
            //        }
            //    }
            //    else
            //    {
            //        //使用付费拍币
            //    }
            //}
            var user = GetUser(userId);
            if (user.Currency < auction.PerPrice)
            {
                result.Message = "余额不足！";
                return result;
            }
            else
            {
                var price = auction.CurrentPrice + 0.01M;
                var second = auction.Interval;
                var expectedTime = now.AddSeconds(second);

                auction.CurrentPrice = price;
                auction.CurrentUserId = userId;
                auction.CurrentNickName = nickname;
                auction.ExpectedTime = expectedTime;

                var data = new {
                    UserId = userId,
                    NickName = nickname,
                    Price = price.ToString("N"),
                    auction.Interval,
                    Ip = ipaddess
                };
                result.Status = true;
                result.Data = data;
                return result;
            }
        }

        internal bool CheckUserLimitation(long userId, Auction auction)
        {
            return false;
        }

        internal void UpdateAuction(Auction auction, long userId, string username, string nickname, string ipaddess)
        {
            var now = DateTime.Now;
            var price = auction.CurrentPrice + 0.01M;
            var second = auction.Interval;
            var expectedTime = now.AddSeconds(second);

            auction.CurrentPrice = price;
            auction.CurrentUserId = userId;
            auction.CurrentNickName = nickname;
            auction.ExpectedTime = expectedTime;

            //var bidding = new Bidding();

            //bidding.Price = auction.PerPrice;
            //bidding.AuctionId = auction.Id;
            //bidding.CurrentPrice = price;
            //bidding.UserId = auction.CurrentUserId;
            //bidding.NickName = auction.CurrentNickName;
            //bidding.CreatedIp = ipaddess;
            //bidding.CreatedBy = username;
            //bidding.CreatedOn = now;

            //return ;
        }

        internal Auction GetAuction(int id)
        {
            if (Auctions.ContainsKey(id))
            {
                return Auctions[id];
            }

            //return null;

            var auction = GetAuctionInternal(id);

            lock (updateStatusLock)
            {
                Auctions[id] = auction;
            }
            return Auctions[id];
        }

        internal Auction GetAuctionInternal(int id)
        {
            const string sql = "select * from [auction] where id=@id";
            using (var connection = new SqlConnection(connectionString).EnsureOpen())
            {
                return connection.Query<Auction>(sql, new { id }).FirstOrDefault();
            }
        }

        internal User GetUser(long id)
        {
            if (id == 1)
            {
                return new User() { Id = 1, Currency = 20 };
            }
            if (id == 2)
            {
                return new User() { Id = 2, Currency = 50 };
            }
            return new User() { Id = 3, Currency = 50 };
        }

        private void LoadDefaultAuctions()
        {
            var list = GetDefaultAuctions();
            list.ForEach(o => Auctions.Add(o.Id, o));
        }

        private static void LoadDefaultTransfiniteUsers()
        {
            TransfiniteUsers.Add(AuctionGrade.Normal, new long[] { 5, 8 });// = new Dictionary<AuctionGrade, long[]>();
        }

        internal List<Auction> GetDefaultAuctions()
        {
            var now = DateTime.Now;
            var builder = new SqlBuilder()
                .Field("*").Table("[Auction]")
                .Where("[Status]<=1");

            var time = new DateTime(now.Year, now.Month, now.Day).AddDays(1);
            builder.Where("[OnlineTime]<@time", new { time });

            var template = builder.AddTemplate(SqlTemplate.Select);
            using (var connection = new SqlConnection(connectionString).EnsureOpen())
            {
                return connection.Query<Auction>(template).ToList();
            }
        }

        private void UpdateStatus(object state)
        {
            // This function must be re-entrant as it's running as a timer interval handler
            if (updatingStatus)
            {
                return;
            }

            lock (updateStatusLock)
            {
                if (!updatingStatus)
                {
                    updatingStatus = true;
                    var now = DateTime.Now;
                    var a = new List<long>();//成交
                    //var b = new List<long>();//下架
                    if (Auctions.Values.Count > 0)
                    {
                        foreach (var auction in Auctions.Values)
                        {
                            if (auction.ExpectedTime != null && now >= auction.ExpectedTime.Value)
                            {
                                //var flag = now.Subtract(auction.ExpectedTime.Value).TotalSeconds > 10;
                                //if (auction.Status == AuctionStatus.Dealt && flag)
                                //{
                                //    b.Add(auction.Id);
                                //}
                                //else
                                //{
                                auction.Status = AuctionStatus.Dealt;
                                a.Add(auction.Id);
                                //}
                            }
                        }
                        foreach (var id in a)
                        {
                            //更新到数据库
                        }

                        if (a.Count > 0)
                        {
                            var data = a.Select(o => new { Id = a, OfflineTime = now });
                            var result = new SkResult<object>(true) { Data = data };
                            Clients.All.statusChanged(result);
                        }
                    }
                    updatingStatus = false;
                }
            }
        }

        //private bool UpdateStockPrice(Auction stock)
        //{
        //    //Randomly choose whether to udpate this stock or not
        //    var r = updateOrNotRandom.NextDouble();
        //    if (r > .1)
        //    {
        //        return false;
        //    }

        //    // Update the stock price by a random factor of the range percent
        //    var random = new Random((int)Math.Floor(stock.CurrentPrice));
        //    var percentChange = random.NextDouble() * RangePercent;
        //    var pos = random.NextDouble() > .51;
        //    var change = Math.Round(stock.CurrentPrice * (decimal)percentChange, 2);
        //    change = pos ? change : -change;

        //    stock.CurrentPrice += change;
        //    return true;
        //}

        private void BroadcastMarketStateChange(MarketState marketState)
        {
            switch (marketState)
            {
                case MarketState.Open:
                    Clients.All.marketOpened();
                    break;
                case MarketState.Closed:
                    Clients.All.marketClosed();
                    break;
                case MarketState.Reset:
                    Clients.All.marketReset();
                    break;
            }
        }

        private void BroadcastStatus(IList<long> ids)
        {
            Clients.All.updateStatus(ids);
        }
    }

    public enum MarketState
    {
        Open,
        Opening,
        Closing,
        Closed,
        Reset
    }

}